/*
* GeoVISTA Center (Penn State, Dept. of Geography)
* Copyright (c), 2003-2011,  Jin Chen and Alan M. MacEachren, GeoVISTA Center, Penn State University
* All Rights Researved.
*
*
* @author: jin Chen
* @date: Jul 10, 2003$
* @version: 1.0
*/
package edu.psu.geovista.app.pcp.plot.control.tools;

import edu.psu.geovista.app.pcp.Pcp;
import edu.psu.geovista.app.pcp.plot.gui.BasicPcpUI;
import edu.psu.geovista.app.pcp.plot.layer.AxisLayer;
import edu.psu.geovista.app.pcp.plot.layer.BasicLayer;
import edu.psu.geovista.app.pcp.plot.layer.ImageLayer;
import edu.psu.geovista.app.plot.axis.AxisList;
import edu.psu.geovista.app.plot.axis.BasicAxis;
import edu.psu.geovista.app.plot.axis.XAxisOrder;
import edu.psu.geovista.app.plot.axis.YAxis;
import edu.psu.geovista.app.plot.layer.Layer;

import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;

//import org.apache.commons.collections.CollectionUtils;

//import org.apache.tools.ant.util.CollectionUtils;

public class AxisPlaceTool extends BasicTool
        implements PropertyChangeListener{
    private XAxisOrder xAxis;
    private boolean fillVertically=true;
    private boolean fillHorizontally=true;//todo tmp false;

    public AxisPlaceTool(BasicPcpUI ui) {
        super(ui);
        this.xAxis=getUI().getPlot().getXaxis() ;
        if (xAxis!=null)
            this.xAxis.addPropertyChangeListener(this);

        updateYAxesPaintMode();// 
    }






    /*******************************************************************************************************
     *                set as valid and visible
     *******************************************************************************************************/
    /**
     *
     * Make only YAxis that specified by validNames as invalid axis, all other all invalid.
     * The ordered of the axes is determined by the order of their names in <validNames>
     * assume length of validNames= length of visibles.
     *  
     * Invalid axis will be removed from yaxislist, however the data for the axis still exist in dataModel
     * @param validNames       validNames.length should match visibles.length
     * @param visibles
     */
    public void setYAxisAsValid(String[] validNames, boolean[] visibles) {
        String[] sortNames=new String[validNames.length ];
        System.arraycopy(validNames,0,sortNames,0,validNames.length );
        Arrays.sort(sortNames);
        HashSet needToAdd=new HashSet();
        HashSet remove=new HashSet();
        for (int i=0;i<sortNames.length ;i++){
            needToAdd.add(sortNames[i]);
        }

        //1 remove invalid axes
        AxisList axisList=this.getAxisList() ;
        Iterator iter=axisList.iterator() ;
        while(iter.hasNext()){
            YAxis axis= (YAxis)iter.next();
            String name=axis.getName();
            int index=Arrays.binarySearch(sortNames,name); // 
            if(index<0){// 
                remove.add(name);
            }
            else{// 
                needToAdd.remove(name);
            }
        }

        iter=remove.iterator() ;
        while(iter.hasNext()){
            String name = (String)iter.next();
            axisList.remove(name);

        }


        //2. add visible (and valid) axes
        //get currently selected variable name
        //ESVisualClassifier vc=this.getUI().getPlot().getVc() ;  esvc  jin temp remove, with new bi-var vc, not need category axis
        //CategoryList ctgList=vc.getCtgList() ;
        //String categoryAxisName=vc.getCurrVariableName() ;//currently selected variable name    esvc  jin temp remove, with new bi-var vc, not need category axis

        Iterator iter2=needToAdd.iterator() ;
        int numOfAxis=axisList.size() +needToAdd.size() ;//The number of axis finally will be
        //
        int axisSize=this.getUI().getPlot().getDefaultYaxisSize() ;
        // 
        while(iter2.hasNext()){
            String axisName = (String)iter2.next();
            Pcp pcp = getUI().getPlot();
            YAxis axis=pcp.generateYAxis(axisName); // createAxis(name)
            axisList.add(axis);
            // if(categoryAxisName.equals(axisName) ){
            //you had removed a category axis before, now when add it again, need show colorbar with it
            //axisList.setCtgAxis(axisName,ctgList); todo esvc  jin temp remove, with new bi-var vc, not need category axis
            // }


        }

        //3. set display index
        for (int i=0;i<validNames.length ;i++){
            String name=validNames[i];
            YAxis axis= axisList.getAxis(name);
            axis.setIndex(i); //it is display index
            //Boolean V=visibles[i];
            axis.setVisible(visibles[i] );
        }




        //4. set axis location
        ArrayList axislist=axisList.getIndexSortAxisList() ;
        Iterator iter3=axislist.iterator() ;
        int index=0;
        while(iter3.hasNext()){
            YAxis axis= (YAxis)iter3.next();
            axis.setIndex(index);
            // Point location=new Point();

            //int x = this.xAxis.getXByIndex(index) ;
            int x = this.xAxis.getLocX(axis) ;
            int y = getUI().getPlot().getOffset().y;
            //axis.setLocation(x,y);
            axis.setAxisLocation(x,y);

            index++;
        }




        /*  
        boolean reclassify=this.updataClassifier(remove,axisList);
        if (reclassify) {
        DataSelectCmd dst=(DataSelectCmd) getCmd(BasicCmd.DataSelectT );
        //dst.showCategory(this.ctgCfg.getCtgList());
        dst.showData();
        }*/

        //it will => setActiveSize() => set Layer, updateYAxisLocation
        boolean updated = xAxis.setNumOfYAxis(axisList.size() );
        if(!updated){//  
            //ui.repaintPlotForChangeSize();
            //ui.get
            repaintDataLayers();
            /* getUI().getPlot().repaint();
            //reSetAllLayer(this.getUI().getPlot().getPlotSize());
            ui.repaintPlotForChangeSize() ;*/
        }


    }

    /*******************************************************************************************************
     *                Time Series
     *******************************************************************************************************/
    /*public void updateTimeSeries(String axisName) {
    DfPcp pcp = this.getUI().getPlot();
    pcp.updateTimeSeries(axisName);
    }*/
    /*******************************************************************************************************
     *               Size
     *******************************************************************************************************/
    /**
     * fit the plot canvas to window size
     * How to fit depend on how  the checkboxes (in config page)  "fill horizontally" and "fill vertically"  are checked.
     */
    public void fitwindow(){
        Pcp plot = this.getUI().getPlot();
        int ppw=((JComponent)plot).getSize().width ; //outline dimension of plot bean
        int pph=((JComponent)plot).getSize().height-plot.getBarOffset() ;//DfPcp.barOffset ;//-30 ;//100 is the height of toolbar and slider bar

        if(ppw>0&&pph>0)
            proposeChangePlotSize(ppw,pph, false) ;
    }
    /**
     *
     * @param ppw
     * @param pph
     * @param alwaysChange  If false, whether update width and height depends on  if the checkboxes
     *                   "fill horizontally" and "fill vertically" is checked.
     *                  If true, always change width and height
     */
    public void proposeChangePlotSize(int ppw,int pph, boolean alwaysChange) {

        //change height,postpone update
        boolean needUpdate=false,updated=false;
        Pcp plot = this.getUI().getPlot();
        if(!Float.isNaN(pph)&&
                (alwaysChange||(!alwaysChange&&this.fillVertically ))) {
            int axisActiveHeight=(int)pph-2*plot.getOffset().y;
            setYAxisHeight(axisActiveHeight,false);
            Point xAxisOffset = plot.getXaxisOffset();

            plot.setXaxisLocation();
            /*int y=plot.getOffset().y+axisActiveHeight+ xAxisOffset.y;// DfPcp.xaxisYOffset;
            xAxis.setAxisLocation(xAxis.getAxisLocation().x, y);*/
            //this.xAxis.setLocation(xAxis.getLocation().x, y);
            needUpdate=true;
        }
        //change width, postpone update
        if (!Float.isNaN(ppw)&&
                (alwaysChange||(!alwaysChange&&this.fillHorizontally ))) {
            int oldWidth=plot.getPlotSize().width ;
            //int oldWidth=((JComponent)plot).getSize().width ;
            int deltaW=(int)ppw-oldWidth;
            updated=proposeChangePlotWidth(deltaW);//=> XAxis's activeSize change =>it should updatePlot automatically


        }

        if(needUpdate &&!updated){
            // 
            // 
            //  
            updatePlotForChangeSize();
        }

    }
    public  void updatePlotForChangeSize(){
        Pcp pcp = this.getUI().getPlot();
        pcp.setBound() ;//set the plot size according to Y/X axes's activesize
        Dimension plotSize = pcp.getPlotSize();
        reSetAllLayer(plotSize);  //reset layers' size = plot size
        // 
        //u need call revalidate() on both of them to make adjustment of viewport size effect.
        /*this.getUI().getPlot().revalidate() ;
        this.ui.getPlotCanvas().revalidate() ;
        this.repaintPlot() ;*/
        ui.repaintPlotForChangeSize() ;//paint again

    }
    /******************************************
     *               Width & interval
     ******************************************/
    /**
     *
     * @param deltaX
     * @return      if true, some change on activesize happen; if false nothing happen.
     */
    public boolean proposeChangePlotWidth(int deltaX) {
        int oldSize = xAxis.getActiveSize();
        int newSize = this.xAxis.proposeChangeActiveSize(deltaX);
        if(oldSize==newSize) return false;
        else return true;
        /*
        this.updateAllYAxisLocX() ;
        if(updatePlot)
        this.updatePlotForChangeSize() ;*/
    }



    public void setAllAsDefaultInteval(int itv,boolean updatePlot) {
        xAxis.setAllAsDefaultInterval(itv);
        /*this.updateAllYAxisLocX() ;
        if(updatePlot)
        this.updatePlotForChangeSize() ;*/
    }
    /* public void setDefaultInteval(int itv) {
    xAxis.setDefaultInterval(itv);
    }*/
    /**************************************************
     *                supports
     ***************************************************/

    public void propertyChange(PropertyChangeEvent evt) {
        String pn=evt.getPropertyName();
        Object src=evt.getSource() ;
        //Integer width=(Integer) evt.getNewValue() ;
        if (src instanceof XAxisOrder){
            if(pn.equals(BasicAxis.ActiveSize ) ){ //XAxis activeSize changed
                //System.out.println("new ActiveSize"+evt.getNewValue());
                this.updateAllYAxisLocX() ;
                //this.getUI().getPlot().setBound() ;
                //Dimension plotSize = this.getUI().getPlot().getPlotSize();
                //reSetAllLayer(plotSize);
                this.updatePlotForChangeSize() ;
            }
            else if(pn.equals(XAxisOrder.PROPERTY_DEFAULT_INTERVAL ) ){
                //Integer Interval = (Integer) evt.getNewValue();
                updateYAxesPaintMode();


            }

        }




    }
    /**
     * if interval is too small (say <40), paint YAxis in simple mode
     */
    public void updateYAxesPaintMode() {
        Pcp pcp = getUI().getPlot();
        XAxisOrder xaxis = pcp.getXaxis();

        int intv=xaxis.getDefaultInterval() ;
        Layer axisLyr= getLayer(Layer.AXIS_LAYER) ;
        Layer dyLyr=getLayer(Layer.DYNAMIC_LAYER);
        if(intv<40){

            axisLyr.setPaintMode(Layer.PAINT_MODE_SIMPLE);
            dyLyr.setPaintMode(Layer.PAINT_MODE_SIMPLE);

            /*AxisList yaxisList = getUI().getPlot().getYaxisList();
            yaxisList.setAxesPaintMode(YAxis.PAINT_MODE_SIMPLE);
            this.repaintPlot();

            //change axes color
            Color color=new Color(132,155,166);
            Layer layer= getLayer(Layer.AXIS_LAYER) ;
            layer.setAConfigInfo(ConfigInfo.COLOR ,color);*/

        }
        else{

            axisLyr.setPaintMode(Layer.PAINT_MODE_FULL);
            dyLyr.setPaintMode(Layer.PAINT_MODE_FULL);
            /*  AxisList yaxisList = getUI().getPlot().getYaxisList();
            yaxisList.setAxesPaintMode(YAxis.PAINT_MODE_FULL);
            this.repaintPlot();
            //change axes color
            Layer layer= getLayer(Layer.AXIS_LAYER) ;
            layer.setAConfigInfo(ConfigInfo.COLOR ,Color.yellow );*/
        }
    }
    //public void updatePlotOnChangedInterval()
    /**
     * reset the location of YAxis based on XAxis's setting
     */
    private void updateAllYAxisLocX(){
        AxisList axisList = getAxisList();
        Iterator iter=axisList.iterator() ;
        while(iter.hasNext()){
            YAxis axis = (YAxis)iter.next();
            this.updateYAxisLocX(axis);



        }
    }
    private void updateYAxisLocX(YAxis axis){
        int y=axis.getLocation().y ;
        //int x= xAxis.getXByIndex(axis.getIndex() );
        int x= xAxis.getLocX(axis );
        //axis.setLocation(x,y);
        axis.setAxisLocation(x,y);
    }
    /**
     *  Since the size of the layer changed, their images become invalid and need repaint
     * @param size
     */
    private void reSetAllLayer(Dimension size){
        Hashtable layers=this.getLayers() ;
        ArrayList layerList=Collections.list(layers.elements())  ;
        Iterator iter=layerList.iterator() ;
        while(iter.hasNext()){
            Layer layer = (BasicLayer)iter.next();
            doDeepClear(layer,size);
            //this.writeImage(layer.getImage() ,layer.getName() );



            /*layer.setSize(size);
            layer.deepClearLayer() ;
            layer.paintLayer() ;*/

        }


    }
    /**
     * Call when u don't need the image anymore, it will put the image for GC.
     * It may happen when size of plot changed
     * @param layer
     * @param size
     */
    private void doDeepClear(Layer layer,Dimension size){
        if(layer instanceof ImageLayer)   {
            ImageLayer l=(ImageLayer)layer;
            l.setSize(size);
            l.deepClearLayer() ;
        }
        layer.paintLayer() ;
    }
    /*******************************************
     *                height
     *******************************************/
    /**
     * set active size of YAxis
     * @param h
     */
    public  void setYAxisHeight(int h){
        this.setYAxisHeight(h,true);
    }
    public void setYAxisHeight(int h,boolean updatePlot){

        Iterator iter=getAxisList().iterator() ;
        while(iter.hasNext()){
            YAxis yaxis= (YAxis)iter.next();
            yaxis.setActiveSize(h);

        }
        if(updatePlot)
            this.updatePlotForChangeSize() ;

    }

    /*******************************************************************************************************
     *                Repaint axis layer
     *******************************************************************************************************/
    //The method can be put in any AxisCmd. The purpose is to repaint axisLayer
    public void repaintAxisLayer() {
        AxisLayer axisLayer=(AxisLayer)this.ui.getLayer(Layer.AXIS_LAYER );
        axisLayer.repaintLayer();

    }

    public void setFillVertically(boolean fillVertically) {
        this.fillVertically = fillVertically;
    }

    public void setFillHorizontally(boolean fillHorizontally) {
        this.fillHorizontally = fillHorizontally;
    }


}




/**
 * The method is to update variable list of VisualClassifier. However, it is not practical:
 * 1.  variable list of VisualClassifier map to its dataModel, update list without updating mode will cause misfunctions. In a word, you need update dataModel
 * 2.  we may need the function that make classification based on variable that is  not display on plot. In the case, we don't have to update variable list of VisualClassifier
 *
 * The method is of no usage. Leave the method to remind you the issues.
 * @param removedList
 * @param finalList
 * @return
 */
/*private boolean updataClassifier(HashSet removedList,AxisList finalList){
boolean reclassify=false;
JVisualClassifier vc=this.getUI().getPlot().getVc() ;
String selected=vc.getCurrVariableName() ;//currently selected variable name

ArrayList names=finalList.getSortAxisNameList() ;
String[] newNames= CollectionUtils.convertToStringArray(names);
Arrays.sort(newNames);
String lastSelectedName=vc.getCurrVariableName() ;
vc.setVariableNames( newNames); //it will set selectedIndex=0

if(removedList.contains(selected) ){//categery axis is removed
String firstAxisName=finalList.getAxis(0).getName() ;

int index=Arrays.binarySearch(newNames,firstAxisName);
vc.setCurrVariableIndex(index);
vc.reClassify(true) ;//do classification again
//dp{
if (DEBUG){
CategoryList ctgList=vc.getCtgList() ;
ctgList.showCategoryList() ;
}//dp}
//finalList.setCtgAxis(firstAxisName,ctgList);
reclassify= true;
}
else{
//vc.setVariableNames( newNames) will set selectedIndex=0 and do classification, must redo classification again
int index=Arrays.binarySearch(newNames,lastSelectedName);
vc.setCurrVariableIndex(index);
vc.reClassify(true) ;//do classification again
reclassify= false;
}
return reclassify;

}*/